package main

import (
	"fmt"
	"net"
	"io"
	"strings"
	"net/http"
	"log"
	"time"
	"sync"
	"errors"
)


// Completment ConnRes interface is a Connect Resourse
type ConnRes interface {
	Close() error
}

// Factory Function to Create Tcp Connect Resource
type Factory func() (ConnRes, error)

// Connection
type Conn struct {
	conn ConnRes
	//连接时间
	time time.Time
}

// Connection Pool
type ConnPool struct {
	// Muti Exclusive Locks
	mu sync.Mutex
	// Channel Save Connect Resource
	conns chan *Conn
	factory Factory
	// Closure Pool
	closed bool
	// TimeOut Time
	connTimeOut time.Duration
}

// Create A Connection Pool
func NewConnPool(factory Factory, cap int, connTimeOut time.Duration) (*ConnPool, error) {
	if cap <= 0 {
		return nil, errors.New("cap can't be less than 0")
	}
	if connTimeOut <= 0 {
		return nil, errors.New("connTimeOut can't be less than 0")
	}

	cp := &ConnPool{
		mu:          sync.Mutex{},
		conns:       make(chan *Conn, cap),
		factory:     factory,
		closed:      false,
		connTimeOut: connTimeOut,
	}
	for i := 0; i < cap; i++ {
		// Use Factory Function to Create Connect Resource
		connRes, err := cp.factory()
		if err != nil {
			cp.Close()
			return nil, errors.New("factory error")
		}
		// Insert Connection Resource into Channel
		cp.conns <- &Conn{conn: connRes, time: time.Now()}
	}

	return cp, nil
}

// Get ConnPool
func (cp *ConnPool) Get() (ConnRes, error) {
	if cp.closed {
		return nil, errors.New("the pool is close ")
	}

	for {
		select {
		// Get Connection From Channel
		case connRes, ok := <-cp.conns:
			{
				if !ok {
					return nil, errors.New("the pool is close ")
				}
				// If TimeOut than Close
				if time.Now().Sub(connRes.time) > cp.connTimeOut {
					connRes.conn.Close()
					continue
				}
				return connRes.conn, nil
			}
		default:
			{
				// If Don't Get Connection Resource From Channel than Create A Connection Resource
				connRes, err := cp.factory()
				if err != nil {
					return nil, err
				}
				return connRes, nil
			}
		}
	}
}

// Put Connection Resource Into Pool
func (cp *ConnPool) Put(conn ConnRes) error {
	if cp.closed {
		return errors.New("the pool is close ")
	}

	select {
	case cp.conns <- &Conn{conn: conn, time: time.Now()}:
		{
			return nil
		}
	default:
		{
			// If Can't Add Than Close
			conn.Close()
			return errors.New("the pool is full ")
		}
	}
}

// Close The Pool
func (cp *ConnPool) Close() {
	if cp.closed {
		return
	}
	cp.mu.Lock()
	cp.closed = true
	// Close The Channel
	close(cp.conns)
	// Close The Connection
	for conn := range cp.conns {
		conn.conn.Close()
	}
	cp.mu.Unlock()
}

// Return Channel Length
func (cp *ConnPool) len() int {
	return len(cp.conns)
}


func main() {

	cp, err_cp := NewConnPool(func() (ConnRes, error) {
		return net.Dial("tcp", "10.10.83.234:9500")
	}, 10, time.Second*100)

	if err_cp != nil {
		fmt.Println("create connection pool failed  ...")
		return
	}

	fmt.Println("create connection pool success，sum is 10 ...")
	fmt.Println(cp)
	fmt.Println("server start ...")
	fmt.Println("1")
	//http.HandleFunc("/dbpool", Db_Container)

	http.HandleFunc("/dbpool", func(w http.ResponseWriter, req *http.Request ) {

		fmt.Println("2")
		// Get Connection
		conn, _ := cp.Get()

		if req.Method == "POST" {
			err := req.ParseForm()
			if err != nil {
				io.WriteString(w, "error")
				return
			}

			io.WriteString(w, "tcp result ")

		}else{

			err := req.ParseForm()
			if err != nil {
				io.WriteString(w, "error")
				return
			}

			sql_value := req.FormValue("sql")
			fmt.Println(conn)
			fmt.Println(sql_value)
			sql := sql_value
			n,err := conn.(net.Conn).Write([]byte(sql))
			buf := make([]byte,65535) // Define Silce
			result_value := ""
			for {
				n,err = conn.(net.Conn).Read(buf)
				result_value += string(buf[:n])
				if err == io.EOF {
					conn.Close()
				}

				if( strings.LastIndex(string(buf[:n]),"\r") > 0){
					break
				}
			}

			cp.Put(conn)
			io.WriteString(w,result_value)
		}
	})

	err := http.ListenAndServe("0.0.0.0:8005", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
	fmt.Println("Over ...")
}

